Skip to content

child_process: watch child_process stdin pipe peer close event#62353

Open
Tseian wants to merge 4 commits intonodejs:mainfrom
Tseian:main
Open

child_process: watch child_process stdin pipe peer close event#62353
Tseian wants to merge 4 commits intonodejs:mainfrom
Tseian:main

Conversation

@Tseian
Copy link
Contributor

@Tseian Tseian commented Mar 20, 2026

Issue

#25131

Description

Watch pipe peer close(EOF/HUP) event, only support for unix. Once the event is detected, a JS callback is triggered to execute, which in turn triggers a method to destroy the socket. Eventually child_process.stdin.on('close') will be triggered.

Before

child_process.stdin.on('close') will not be triggered if the readable end of the pipe has been closed.

After

child_process.stdin.on('close') will be triggered if the readable end of the pipe has been closed.

Changes

Test

@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. labels Mar 20, 2026
@Tseian Tseian changed the title child-process: watch pipe peer close event WIP child-process: watch pipe peer close event Mar 20, 2026
@Tseian Tseian force-pushed the main branch 3 times, most recently from 071fa18 to 4cedb07 Compare March 21, 2026 06:11
@codecov
Copy link

codecov bot commented Mar 21, 2026

Codecov Report

❌ Patch coverage is 72.72727% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.68%. Comparing base (2263b4d) to head (554052d).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
src/pipe_wrap.cc 69.64% 5 Missing and 12 partials ⚠️
lib/internal/child_process.js 90.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #62353      +/-   ##
==========================================
- Coverage   89.68%   89.68%   -0.01%     
==========================================
  Files         676      676              
  Lines      206710   206772      +62     
  Branches    39584    39609      +25     
==========================================
+ Hits       185398   185438      +40     
- Misses      13441    13444       +3     
- Partials     7871     7890      +19     
Files with missing lines Coverage Δ
src/pipe_wrap.h 100.00% <ø> (ø)
lib/internal/child_process.js 94.69% <90.00%> (+0.03%) ⬆️
src/pipe_wrap.cc 77.52% <69.64%> (-3.78%) ⬇️

... and 26 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Tseian Tseian force-pushed the main branch 2 times, most recently from 0ae2360 to 09463c0 Compare March 21, 2026 08:21
@Tseian Tseian changed the title WIP child-process: watch pipe peer close event child-process: watch pipe peer close event Mar 21, 2026
@Tseian Tseian force-pushed the main branch 2 times, most recently from 9e25a20 to 78acd67 Compare March 21, 2026 14:26
@Tseian Tseian changed the title child-process: watch pipe peer close event child-process: watch child_process stdin pipe peer close event Mar 21, 2026
@Tseian Tseian force-pushed the main branch 3 times, most recently from e704d96 to 8cc7608 Compare March 22, 2026 01:43
@Tseian Tseian changed the title child-process: watch child_process stdin pipe peer close event child_process: watch child_process stdin pipe peer close event Mar 22, 2026
@Tseian
Copy link
Contributor Author

Tseian commented Mar 22, 2026

@nodejs-github-bot retest

@Tseian
Copy link
Contributor Author

Tseian commented Mar 23, 2026

@jasnell @ronag @jbunton-atlassian Hi everyone, Could you please have a code review for this PR?

@Tseian
Copy link
Contributor Author

Tseian commented Mar 24, 2026

@jasnell Please have a code review again for this PR.

  • Deleted wnwatchPeerClose method
  • watchPeerClose no longer returns values

@addaleax addaleax added the child_process Issues and PRs related to the child_process subsystem. label Mar 24, 2026
@addaleax addaleax added the semver-major PRs that contain breaking changes and should be released in the next major version. label Mar 24, 2026
Comment on lines +165 to +168
PipeWrap::~PipeWrap() {
peer_close_watching_ = false;
peer_close_cb_.Reset();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't do anything/will just be optimized out by the compiler anyway

static void Fchmod(const v8::FunctionCallbackInfo<v8::Value>& args);

bool peer_close_watching_ = false;
v8::Global<v8::Function> peer_close_cb_;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit/optional: I'd consider using an internal field over a Global. The only reason this doesn't create a memory leak is because PipeWrap instances are always strong references, and that will probably keep being true, but it's still an extra assumption we don't really need.

}

wrap->peer_close_watching_ = false;
wrap->peer_close_cb_.Reset();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't an empty peer_close_cb_ always correspond to peer_close_watching_ being false? That means we're keeping the same state twice

Comment on lines +308 to +309
errors::TryCatchScope try_catch(env);
try_catch.SetVerbose(true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't be necessary, right? We don't do this for other callbacks from libuv events either

Comment on lines +337 to +342
if (watchPeerClose &&
process.platform !== 'win32' &&
typeof pipe?.watchPeerClose === 'function') {
pipe.watchPeerClose(true, () => sock.destroy());
sock.once('close', () => pipe.watchPeerClose(false));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is quite a bit of extra code, but wouldn't just adding an unconditional sock.resume(); give the same result? That way we keep reading (but not consuming) data from the socket until we hit EOF, and since allowHalfOpen isn't set, that should also close the writable side of the stream. In other words, if I'm not missing anything (and which is completely possible), this PR can be shortened to:

Suggested change
if (watchPeerClose &&
process.platform !== 'win32' &&
typeof pipe?.watchPeerClose === 'function') {
pipe.watchPeerClose(true, () => sock.destroy());
sock.once('close', () => pipe.watchPeerClose(false));
}
socket.resume();

(plus a comment or so about 'why' we do that, ofc)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++ Issues and PRs that require attention from people who are familiar with C++. child_process Issues and PRs related to the child_process subsystem. lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. semver-major PRs that contain breaking changes and should be released in the next major version.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants